package net

import (
	"encoding/json"
	"fmt"
	"net"
)

const UDPMessageSize = 512

type NetUDPEventHandler func(*NetUDP, *NetPacket)

type NetUDP struct {
	sock *net.UDPConn

	in  chan *NetPacket
	out chan *NetPacket

	// Handle incoming packets read from the socket
	handlers []NetUDPEventHandler
}

// Registers an event handler which is invoked on incoming packets.
func (this *NetUDP) AddHandler(f NetUDPEventHandler) {
	this.handlers = append(this.handlers, f)
}

func (this *NetUDP) ListenAndServe(port string, f NetUDPEventHandler) (err error) {

	var addr *net.UDPAddr

	if addr, err = net.ResolveUDPAddr("udp4", ":"+port); err != nil {
		fmt.Println("error: ListenUDP() laddr: ", err)
		return err
	}

	if this.sock, err = net.ListenUDP("udp4", addr); err != nil {
		fmt.Println("error: ListenUDP() ", err)
		return err
	}

	this.handlers = append(this.handlers, f)

	go this.handleReceiving()
	go this.handleSending()
	go this.handleDispatching()

	return nil
}

func (this *NetUDP) Send(msg interface{}, addr string) {

	switch v := msg.(type) {
	case []byte:
		this.out <- &NetPacket{addr, v}
	case string:
		this.out <- &NetPacket{addr, []byte(v)}
	case map[string]string:
		mb, _ := json.Marshal(v)
		this.out <- &NetPacket{addr, mb}
	default:
	}
}

func (this *NetUDP) handleDispatching() {
	for p := range this.in {
		for _, f := range this.handlers {
			go f(this, p)
		}
	}
}

func (this *NetUDP) handleReceiving() {
	for {
		var buf [UDPMessageSize]byte
		n, addr, err := this.sock.ReadFromUDP(buf[0:])
		if err != nil {
			fmt.Println("error receiving(): ", err)
		}

		msg := make([]byte, n)
		copy(msg, buf[0:n])

		this.in <- &NetPacket{addr.String(), msg}
	}
}

func (this *NetUDP) handleSending() {

	for p := range this.out {

		//fmt.Println("Udp Send", p)

		go func() {

			if p == nil {
				return
			}

			if p.Addr == "" {
				return
			}

			addr, err := net.ResolveUDPAddr("udp4", p.Addr)
			if err != nil {
				fmt.Println("error: handleSending() invalid p.Addr")
				return
			}

			if _, err = this.sock.WriteTo(p.Body, addr); err != nil {
				fmt.Println("error: handleSending() ", addr.String(), err)
			}
		}()
	}
}
